/**
 * @file
 * @brief Defines the CanbusHandlerclass.
 */

#ifndef _WS_CANBUS_H_
#define _WS_CANBUS_H_

#include <iostream>
#include <algorithm>
#include <array>
#include <memory>
#include <mutex>
#include <thread>
#include <unordered_map>
#include <vector>
#include <chrono>
#include <string>

#include <canlib.h>

#include "ros/ros.h"

#include "canbus/byte.h"
#include "canbus/message.h"
#include "canbus/consts.h"
#include "canbus/can_messages.h"

#define FNAME "/"  << __FUNCTION__ << "/ "

/**
 * @namespace canbus
 * @brief canbus
 */
namespace canbus {

/**
 * @struct CanbusHandler
 * @brief The struct that defines Receiving data from canbus
 */
struct CanRecvData {
public:

  /**
   * @brief Constructor default.
   */
  CanRecvData():
  id(0),
  msg(),
  dlc(8),
  flag(),
  time(),
  timeout(0xFFFFFFFF)
  {}

  long id;
  uint8_t msg[8];
  unsigned int dlc;
  unsigned int flag;
  unsigned long time;
  unsigned long timeout;  
};

/**
 * @struct CanbusHandler
 * @brief The struct that defines Sending data to canbus
 */
struct CanSendData {
public:

  /**
   * @brief Constructor default.
   */
  CanSendData():
  id(0),
  msg(),
  dlc(8),
  flag(canMSG_STD)
  {}

  long id;
  uint8_t msg[8];
  unsigned int dlc;
  unsigned int flag;
};

/**
 * @class PHYLayerHandler
 * @brief The class that defines the base abstract 
 *        class for Physical layer communication
 */
class PHYLayerHandler {
public:
  PHYLayerHandler() = default;
  virtual ~PHYLayerHandler() = default;
  virtual bool Write(const MsgBase* data) = 0;
  virtual bool Read(MsgBase* data) = 0;
  virtual bool Init() = 0;
  virtual bool Configure() = 0;
protected:
  // std::string id_;
};

/**
 * @class CanbusHandler
 * @brief The class of wrapping kvaser canbus handler.
 */
class CanbusHandler: public PHYLayerHandler {
public:

  /**
   * @brief Constructor which takes a pointer to a one-byte unsigned integer.
   * @param value The pointer to a one-byte unsigned integer for construction.
   */
  explicit CanbusHandler(std::string id, int channel, long can_rate, uint8_t can_msg_type);

  /**
   * @brief Destructor.
   */
  ~CanbusHandler();

  /**
   * @brief Initialize all the canbus-related handlers
   * @return success or not
   */
  bool Init() override;

  /**
   * @brief Configure all the parameters in handlers.
   * @return success or not 
   */
  bool Configure() override;
  
  /**
   * @brief public write function that writes data to canbus
   * @param data The constant pointer of the data that to be write.
   * @return success or not
   */
  bool Write(const MsgBase* data) override;
  
  /**
   * @brief public read function that read data from canbus
   * @param data The pointer of the data that to be read.
   * @return success or not
   */
  bool Read(MsgBase* data) override;
  
  /**
   * @brief private canbus write function that writes data to canbus
   * @return success or not
   */
  bool CanWrite();
  
  /**
   * @brief private canbus read function that read data from canbus
   * @return success or not
   */
  bool CanRead();
  
  /**
   * @brief Start the recv and send thread functions.
   * @return success or not
   */
  bool Start();
  
  /**
   * @brief Stop and destroy the recv and send thread functions.
   * @return success or not
   */
  bool Stop();
  
  /**
   * @brief the implementation of recv thread functions.
   */
  void RecvThreadFunc();
  
  /**
   * @brief the implementation of send thread functions.
   */
  void SendThreadFunc();
  
  /**
   * @brief register various types of canbus messages to this handle
   */
  template<typename T>
  void RegisterCanMsg() {
    can_msg_map_.emplace(T::ID, new T());
  }
  
  /**
   * @brief clear the contents of all the canbus messages pointer
   */
  void ResetMessages();
  
  /**
   * @brief find the corresponding pointer to a specific canbus message id.
   * @param id canbus id to be indexing.
   * @return the pointer of indexed canbus message
   */
  MsgBase* GetDataById(const uint32_t id);
  
  /**
   * @brief get the name of this handle.
   * @return the name of this handle.
   */
  std::string getID() const {
    return id_;
  }
  
  /**
   * @brief check whether the recv and send threads are running
   * @return running or not
   */
  bool IsRunning() const {
    return is_running_;
  }

private:
  std::string id_;
  int channel_id_;
  long can_rate_;
  unsigned int can_msg_type_;
  canHandle tx_, rx_;

  bool is_running_;
  std::unique_ptr<std::thread> tx_thread_, rx_thread_;

  CanSendData send_buf;
  CanRecvData recv_buf;
  std::unordered_map<uint32_t, MsgBase*> can_msg_map_;
};

}

#endif // _WS_CANBUS_H_